/*
 * SIT_Event.c : main window procedure handler for top-level windows and sub-classed
 *               controls. Also dispatch events to user callbacks.
 *
 * Written by T.Pierron, Feb 2006.
 */

#include "SIT_P.h"
#include "graphics_P.h"

LPWSTR win32_predef[] = {
	IDC_ARROW,
	IDC_ARROW, IDC_APPSTARTING, IDC_HAND, IDC_CROSS, IDC_HELP, IDC_IBEAM, IDC_NO,
	IDC_SIZEALL, IDC_SIZENESW, IDC_SIZENS, IDC_SIZENWSE, IDC_SIZEWE,
	IDC_WAIT, IDC_HAND
};

DLLIMP int SIT_ApplyCallback(SIT_Widget w, APTR cd, int type)
{
	if (w == NULL) return -1;

	SIT_Callback cb, next;
	int oldf = w->sw_Flags & SITF_IsLocked, ret = 0;

	for (cb = next = (SIT_Callback) w->sw_Callbacks.lh_Head; cb; cb = next)
	{
		next = (APTR) cb->sc_Node.ln_Next;
		if (cb->sc_Event != type) continue;

		/*
		 * Here's the problem:
		 * User callback might indirectly call DestroyWindow(), which will send (and
		 * not post) a WM_DESTROY message, which in turn, will free all children.
		 * Since we have no mean at this point to know if the hierarchy has been
		 * freed, we need to lock the structure, and free it when we are in a safe spot.
		 */
		w->sw_Flags |= SITF_IsLocked;

		ret = cb->sc_CB(w, cd, cb->sc_UserData);

		if (ret || ! next) break;
	}
	w->sw_Flags &= ~SITF_IsLocked;
	w->sw_Flags |= oldf;
	return ret;
}

/* WM_PAINT handler */
static Bool SIT_HandlePaint(SIT_Widget w, HWND wnd, int type)
{
	SIT_Callback cb;
	PAINTSTRUCT  ps;
	HDC          hdc;
	GCBuf        gc;
	Bool         done = False;

	for (cb = HEAD(w->sw_Callbacks); cb; NEXT(cb))
	{
		if (cb->sc_Event != type) continue;

		if (! done)
		{
			w->sw_Flags |= SITF_IsLocked;
			hdc = BeginPaint(wnd, &ps);
			GFX_InitGC(&gc, w->sw_Handle, hdc, &ps.rcPaint, w->sw_Font, w->sw_BgBrush);
			done = True;
		}
//		fprintf(stderr, "WM_PAINT handler for '%S': %dx%d - %dx%d\n", w->sw_Name, ps.rcPaint.left, ps.rcPaint.top,
//			ps.rcPaint.right, ps.rcPaint.bottom);
		cb->sc_CB(w, &gc, cb->sc_UserData);
	}
	if (done)
	{
		GFX_Free(&gc);
		EndPaint(wnd, &ps);
		w->sw_Flags &= ~SITF_IsLocked;
	}

	return done;
}

uint8_t sit2vk[] = { /* Must follow order of SITK_* enum */
	VK_HOME,   VK_END,     VK_PRIOR,    VK_NEXT,   VK_UP,      VK_DOWN,     VK_LEFT,
	VK_RIGHT,  VK_LSHIFT,  VK_RSHIFT,   VK_LMENU,  VK_RMENU,   VK_LCONTROL, VK_RCONTROL,
	VK_LWIN,   VK_RWIN,    VK_APPS,     VK_RETURN, VK_CAPITAL, VK_INSERT,   VK_DELETE,
	VK_SCROLL, VK_NUMLOCK, VK_SNAPSHOT, VK_F1,     VK_F2,      VK_F3,       VK_F4,
	VK_F5,     VK_F6,      VK_F7,       VK_F8,     VK_F9,      VK_F10,      VK_F11,
	VK_F12,    VK_F13,     VK_F14,      VK_F15,    VK_F16,     VK_F17,      VK_F18,
	VK_F19,    VK_F20,     VK_TAB,      VK_BACK,   VK_ESCAPE,  VK_SPACE,    VK_HELP,
	VK_SHIFT
};

/* Convert keysym of OS widget layer into our defines */
static Bool SIT_HandleKeyboard(SIT_Widget w, ULONG key, int flags)
{
	SIT_Callback cb;
	SIT_OnKey    keybuf = {};
	int          type = (flags == SITK_Virtual ? SITE_OnRawKey : SITE_OnVanillaKey);

	for (cb = HEAD(w->sw_Callbacks); cb; NEXT(cb))
	{
		if (cb->sc_Event != type) continue;

		if (keybuf.keycode == 0)
		{
			w->sw_Flags |= SITF_IsLocked;
			if (GetKeyState(VK_SHIFT) < 0)   flags |= SITK_FlagShift;
			if (GetKeyState(VK_CONTROL) < 0) flags |= SITK_FlagCtrl;
			if (GetKeyState(VK_MENU) < 0)    flags |= SITK_FlagAlt;
			keybuf.flags = flags;

			if (flags & SITK_Virtual)
			{
				DATA8 ptr = memchr(sit2vk, key, sizeof sit2vk);

				if (ptr == NULL)
				{
					/* 96 through 111 are keypad message, we'll receive WM_CHAR instead */
					if (96 <= key && key <= 111)
						return False;
					// else if (! ('A' <= key && key <= 'Z'))
					//	key |= SITK_OEM;
				}
				else key = SITK_Home + (ptr - sit2vk);
			}
			keybuf.keycode = key;

			if (key <= 0x10FFFF)
			{
				/* Convert code point to UTF-8 if it is a character */
				WCHAR wbuf[2];
				int   len;
				if (key >= 0x10000)
				{
					key -= 0x10000; len = 2;
					wbuf[0] = 0xD800 + (key & 0x3ff);
					wbuf[1] = 0xDC00 + (key >> 10);
				}
				else wbuf[0] = key, len = 1;
				len = WideCharToMultiByte(CP_UTF8, 0, wbuf, len, keybuf.utf8, sizeof keybuf.utf8,
					NULL, 0);
				keybuf.utf8[len] = 0;
			}
			else keybuf.utf8[0] = 0;
		}

		if (cb->sc_CB(w, &keybuf, cb->sc_UserData)) {
			w->sw_Flags &= ~SITF_IsLocked;
			return True;
		}
	}
	w->sw_Flags &= ~SITF_IsLocked;
	return False;
}

/* Scrollbar messages */
Bool SIT_HandleScrollBar(SIT_Widget w, int type)
{
	SIT_ScrollBar sb  = (SIT_ScrollBar) w;
	SIT_Callback  cb  = NULL;
	LONG          pos = sb->sb_Info.nPos;

	/*
	 * Compute new position even if user didn't have registered a callback for
	 * scrollbar events. Win32 common control won't move until we tell it to
	 */
	switch (type) {
	case SB_PAGEDOWN: pos += sb->sb_Info.nPage; break;
	case SB_LINEDOWN: pos += sb->sb_LineHeight; break;
	case SB_PAGEUP:   pos -= sb->sb_Info.nPage; break;
	case SB_LINEUP:   pos -= sb->sb_LineHeight; break;
	case SB_TOP:      pos = sb->sb_Info.nMin; break;
	case SB_BOTTOM:   pos = sb->sb_Info.nMax; break;
	case SB_THUMBPOSITION:
	case SB_THUMBTRACK:
		/* wParam only gives us the loword of the 32bit position */
		sb->sb_Info.fMask = SIF_TRACKPOS;
		if (GetScrollInfo(w->sw_Handle, SB_CTL, &sb->sb_Info))
			pos = sb->sb_Info.nTrackPos, sb->sb_Info.fMask = 0;
		else
			return False; /* Not a scrollbar ?! */
		break;
	case SB_ENDSCROLL: return False;
	}
	if (pos < sb->sb_Info.nMin) pos = sb->sb_Info.nMin;
	if (pos > sb->sb_Info.nMax - sb->sb_Info.nPage + 1)
		pos = sb->sb_Info.nMax - sb->sb_Info.nPage + 1;

	sb->sb_IsDragged = type == SB_THUMBTRACK;
	for (cb = HEAD(w->sw_Callbacks); cb; NEXT(cb))
	{
		if (cb->sc_Event != SITE_OnScroll) continue;

		/* Pass data to user callback */
		cb->sc_CB(w, (APTR) pos, cb->sc_UserData);
	}

	if (pos != sb->sb_Info.nPos)
	{
		SetScrollPos(w->sw_Handle, SB_CTL, pos, TRUE);
		sb->sb_Info.nPos = pos;
		return True;
	}

	return False;
}

/*
 * Handler for slider. Much simpler than scrollbar since we don't have to
 * explicitely set its position
 */
static void SIT_HandleSlider(SIT_Widget w, int code, Bool doBuddy)
{
	SIT_Slider s = (SIT_Slider) w;
	ULONG pos = SendMessage(w->sw_Handle, TBM_GETPOS, 0, 0);

	if (pos != s->tb_SliderPos)
	{
		s->tb_SliderPos = pos;
		SIT_ApplyCallback(w, (APTR) pos, SITE_OnScroll);
		if (s->tb_Buddy && doBuddy)
		{
			WCHAR number[16];
			wsprintf(number, L"%d", s->tb_SliderPos);
			SetWindowText(s->tb_Buddy->sw_Handle, number);
		}
	}
}

/* Handle pre and post edition events */
static void SIT_HandleEdit(SIT_Widget w, int event)
{
	/* Check first if we need to track anything */
	SIT_Callback cb;
	SIT_EditBox  ed = (SIT_EditBox) w;

	for (cb = HEAD(w->sw_Callbacks); cb; NEXT(cb))
	{
		if (cb->sc_Event != event) continue;
		if (ed->se_CancelEvent) { ed->se_CancelEvent = False; return; }

		SIT_EditGetText(ed);
		SendMessage(w->sw_Handle, EM_GETSEL, (WPARAM) &ed->se_StartSel, (LPARAM) &ed->se_EndSel);

		/* Already changed */
		if (ed->se_EditType == SITV_HotKey)
		{
			if (cb->sc_CB(w, (APTR) ed->se_HotKey, cb->sc_UserData)) break;
		}
		else if (cb->sc_CB(w, w->sw_Title, cb->sc_UserData))
		{
			/*
			 * User modified our title string. It is a little bit quick'n dirty but is
			 * usually enough for common cases. Otherwise you will have to set SIT_Title
			 * property
			 */
			int ch;
			/* Pos will be reset */
			SendMessage(w->sw_Handle, EM_GETSEL, (WPARAM) &ed->se_StartSel, (LPARAM) &ed->se_EndSel);
			ed->se_CancelEvent = True;
			SIT_SetTitle(w);
			ch = MultiByteToWideChar(CP_UTF8, 0, w->sw_Title, -1, NULL, 0)-1;
			if (ed->se_StartSel == ed->se_EndSel && ch < ed->se_EndSel)
				ed->se_StartSel = ed->se_EndSel = ch+1;
			Edit_SetSel(w->sw_Handle, ed->se_StartSel-1, ed->se_EndSel-1);
		}
	}

	if (event == SITE_OnChanged && ed->se_BuddyTB)
	{
		char number[16];
		GetWindowTextA(w->sw_Handle, number, DIM(number));
		event = atoi(number);
		SendMessage(ed->se_BuddyTB->sw_Handle, TBM_SETPOS, TRUE, event);
		SIT_HandleSlider(ed->se_BuddyTB, event, False);
	}
}

static void SIT_HandleResize(SIT_Widget w)
{
	Rect * box = &w->sw_Box;
	RECT   sz;

	if (w->sw_Type == SIT_DIALOG) GetClientRect(w->sw_Handle, &sz);
	else GetWindowRect(w->sw_Handle, &sz);

	sz.right  += box->x - sz.left;
	sz.bottom += box->y - sz.top;

	if (sz.right == 0 && sz.bottom == 0) return; // Minimize

//	fprintf(stderr, "Got WM_SIZE event (%S : %ldx%ld - %dx%d).\n", w->sw_Name,
//		sz.right, sz.bottom, box->x, box->y);

	if (box->width != sz.right || box->height != sz.bottom)
	{
//		fprintf(stderr, "resizing %S from %dx%d to %ldx%ld\n", w->sw_Name, box->width, box->height, sz.right, sz.bottom);
		box->width  = sz.right;
		box->height = sz.bottom;
		if (w->sw_Type == SIT_DIALOG)
		{
			SIT_Dialog d = (SIT_Dialog) w;
			w->sw_Flags &= ~(SITF_FixedWidth | SITF_FixedHeight);
			if (d->sd_Status)
			{
				GetWindowRect(d->sd_Status->sw_Handle, &sz);
				box->height -= sz.bottom - sz.top;

				SIT_SetStatusPart(d->sd_Status);

				/* Does not seem to track size by itself otherwise */
				SendMessage(d->sd_Status->sw_Handle, WM_SIZE, 0, 0);
			}
			if (SIT_LayoutWidgets(w, KeepDialogSize) == FALSE)
				SIT_Log(SIT_CRITICAL, "Error laying out widgets from '%S' (usually check for circular ref among attachments)", w->sw_Name);
			SIT_MoveWidgets(w);
		}
	}
}

/* Get files/folder path being drop on the window */
static void SIT_HandleDropFiles(SIT_Widget w, HANDLE hDrop)
{
	/* Compute size for storing list of path */
	int i, sz, max, count = DragQueryFile(hDrop, 0xFFFFFFFF, NULL, 0);

	for (i = sz = max = 0; i < count; i ++) {
		int bytes = (DragQueryFile(hDrop, i, NULL, 0) << 1) + 2;
		if (max < bytes) max = bytes;
		sz += bytes;
	}

	int    tblsz  = (count + 1) * sizeof (STRPTR);
	STRPTR buffer = malloc(tblsz + sz), p;
	LPWSTR temp   = alloca(max);
	int    remain = sz;

	for (p = buffer + tblsz, i = 0; i < count; i ++)
	{
		/* Get UTF16 encoded path, stored in UTF8 */
		DragQueryFile(hDrop, i, temp, max);
		int len = WideCharToMultiByte(CP_UTF8, 0, temp, -1, p, remain, NULL, 0);
		while (len == 0) /* Buffer too small */
		{
			/*
			 * sz was the size in UTF16, we alloced the same amount for UTF8
			 * therefore there is a slight chance UTF8 might be bigger.
			 */
			buffer = realloc(buffer, tblsz + (sz += 256));
			p = buffer + tblsz; remain += 256;
			len = WideCharToMultiByte(CP_UTF8, 0, temp, -1, p, remain, NULL, 0);
		}
		remain -= len;
		p += len;
	}
	DragFinish(hDrop);
	/* Set table */
	for (i = 0, p = buffer + tblsz; i < count; i ++, p = strchr(p, 0) + 1)
		((STRPTR *)buffer)[i] = p;
	((STRPTR *)buffer)[i] = NULL;
	SIT_ApplyCallback(w, buffer, SITE_OnDropFiles);
	free(buffer);
}

int SIT_CalcBorderSize(SIT_Widget w, APTR cd, APTR ud);

/* Send WM_NCPAINT only (no WM_PAINT) */
static void SIT_RefreshBorder(SIT_Widget w)
{
	RECT cli, r = {};
	HWND wnd = w->sw_Handle;

	SIT_CalcBorderSize(w, &r, &((SIT_Canvas)w)->sc_Frame);
	GetClientRect(w->sw_Handle, &cli);
	OffsetRect(&cli, r.left, r.top);

	HDC hdc = GetWindowDC(wnd);
	ExcludeClipRect(hdc, cli.left, cli.top, cli.right, cli.bottom);
	SIT_ApplyCallback(w, hdc, SITE_OnNCPaint);
	ReleaseDC(wnd, hdc);
}

/*
 * Menu icons handlers, for WinXp and below.
 */
#define dialog ((SIT_Dialog)w)
static Bool SIT_MeasureMenu(SIT_Widget w, LPMEASUREITEMSTRUCT mis)
{
	MENUITEMINFO inf = {.cbSize = sizeof inf, .fMask = MIIM_DATA | MIIM_FTYPE};
	if (mis->itemID > 0 && GetMenuItemInfo(dialog->sd_MenuBar, mis->itemID, FALSE, &inf) &&
	  !(inf.fType & MFT_RADIOCHECK) && 0 < inf.dwItemData && inf.dwItemData <= dialog->sd_MICount)
	{
		int min = GetSystemMetrics(SM_CXMENUCHECK);
		SIT_MenuIcon mi = dialog->sd_MenuIcons + (inf.dwItemData - 1);
		mis->itemWidth = mi->width < min ? 0 : mi->width - min;
		mis->itemHeight = mi->height;
		return True;
	}
	return False;
}

static void SIT_DrawMenuItem(SIT_Widget w, LPDRAWITEMSTRUCT dis)
{
	MENUITEMINFO inf = {.cbSize = sizeof inf, .fMask = MIIM_DATA | MIIM_STATE | MIIM_FTYPE};
	/* Do not draw icons for: checkmark/bullet menu, menu marked as having no icon (SITM_NoMenuIcon) */
	if (dis->itemID > 0 && GetMenuItemInfo(dialog->sd_MenuBar, dis->itemID, FALSE, &inf) &&
	  !(inf.fType & MFT_RADIOCHECK) && 0 < inf.dwItemData && inf.dwItemData <= dialog->sd_MICount)
	{
		SIT_MenuIcon mi = dialog->sd_MenuIcons + (inf.dwItemData - 1);
		int disa = (inf.fState & MFS_GRAYED) && ! (dis->itemState & ODS_SELECTED);
		int min  = GetSystemMetrics(SM_CXMENUCHECK);
		int x    = dis->rcItem.left - min - 2;
		if (mi->width < min) x = dis->rcItem.left - ((min + mi->width) >> 1);
		if (mi->img)
		{
			Image img = mi->img;
			GCBuf gc;
			if (img->bpp == 32 && disa)
			{
				/* Yeaargl. This stupid API cannot deal with 32bpp bitmap. Of course, would be too easy */
				BITMAPINFOHEADER bmi = {.biSize = sizeof bmi, .biWidth = mi->width, .biHeight = -mi->height, .biPlanes = 1,
					.biBitCount = 24};
				DATA8   data, s, d;
				int     i, j;
				HDC     memDC = CreateCompatibleDC(NULL);
				HBITMAP hbmp = CreateDIBSection(memDC, (LPBITMAPINFO)&bmi, DIB_RGB_COLORS, (LPVOID) &data, NULL, 0);
				HBITMAP old  = SelectObject(memDC, hbmp);
				bmi.biSizeImage = (bmi.biWidth * 3 + 3) & ~3;

				for (s = img->bitmap+3, d = data, j = mi->height; j > 0; j --, d += bmi.biSizeImage)
				{
					DATA8 r;
					for (r = d, i = mi->width; i > 0; r[0] = r[1] = r[2] = *s > 127?0:255, s+=4, r+=3, i --);
				}

				/* The API does not appear to handle drawing color bitmap on disable items at all. Only draw mask (alpha channel) */
				BitBlt(dis->hDC, x, dis->rcItem.top, img->width, mi->height, memDC, 0, 0, SRCAND);

				SelectObject(memDC, old);
				DeleteObject(hbmp);
				DeleteDC(memDC);
			}
			else
			{
				/* Will alloc nothing ; therefore nothing to free */
				GFX_InitGC(&gc, NULL, dis->hDC, &dis->rcItem, NULL, NULL);
				GFX_SetPixels(img, 0, 0, img->width, mi->height,
							  &gc, x, dis->rcItem.top, img->width, mi->height);
			}
		}
		else /* Draw from imagelist */
		{
			pImageList_Draw(mi->imgList, mi->imgId, dis->hDC, x, dis->rcItem.top, ILD_NORMAL);
		}
	}
}
#undef dialog

/*
 * Process window message and dispatch them to user handler
 */
LRESULT CALLBACK SIT_SubClassHandler(HWND wnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	SIT_Widget w = (SIT_Widget) GetProp(wnd, sit_class);
	int param = 0;
	int ret   = 0;

	if (w == NULL) return DefWindowProc(wnd, message, wParam, lParam);

	switch (message) {
	case WM_SHOWWINDOW:
		if (w->sw_Type == SIT_DIALOG)
		{
			w->sw_Visible = wParam;
			#ifdef DEBUG
			fprintf(stderr, "Got WM_SHOWWINDOW = %d (%S)\n", wParam, w->sw_Name);
			#endif
		}
		return 1;

	#define	dlg   ((SIT_Dialog)w)
	case WM_CLOSE:
		/* Give user a chance to cancel closing action */
		param = True;
		if (HAS_EVT(w, SITE_OnClose))
			SIT_ApplyCallback(w, &param, SITE_OnClose);
		if (param)
		{
			/* Has to be enabled now, to allow windows to activate the parent dialog */
			if (dlg->sd_Modal) EnableWindow(dlg->sd_Modal, TRUE), dlg->sd_Modal = NULL;
			/* Will send WM_DESTROY */
			DestroyWindow(w->sw_Handle);
			return 0;
		}
		break;

	case WM_DESTROY:
		/* Window is about to be destroyed, cleanup our stuff */
		ret = (w->sw_Type == SIT_DIALOG && dlg->sd_Type == SITV_ModalEvt);
		SIT_DestroyChildren(w);
		SIT_DestroyWidget(w);

		/* No more windows opened => exit app */
		if (app->super.sw_Children.lh_Count == 0)
			PostQuitMessage(0);
		else if (ret && ! exiting)
			exiting = 2;

		return 0;
	#undef dlg

	case WM_PAINT:
		if (! HAS_EVT(w, SITE_OnPaint) || ! SIT_HandlePaint(w, wnd, SITE_OnPaint))
			return DefWindowProc(wnd, message, wParam, lParam);
		break;

	case WM_SIZE: /* Window size changed */
		if (w->sw_Flags & SITF_GeometrySet)
		{
			/* Event comes from layout - no need to restart the whole process */
			w->sw_Flags &= ~SITF_GeometrySet;
			if (w->sw_Flags & SITF_HandleEraseBg) /* but we will still get a ERASEBKGND */
				w->sw_Flags |= SITF_PostProcess;
		}
		else if (w->sw_Visible)
		{
			if (w->sw_Flags & SITF_HandleEraseBg)
				w->sw_Flags |= SITF_PostProcess;
			SIT_HandleResize(w);
			if (HAS_EVT(w, SITE_OnResize))
			{
				ULONG sz[] = {w->sw_Box.width - w->sw_Box.x, w->sw_Box.height - w->sw_Box.y};
				if (w->sw_Flags & SITF_NCMargins)
					sz[0] -= w->sw_Margins[0] + w->sw_Margins[2],
					sz[1] -= w->sw_Margins[1] + w->sw_Margins[3];
				SIT_ApplyCallback(w, sz, SITE_OnResize);
			}
		}
		if (w->sw_Type == SIT_TOOLBAR && w->sw_Visible)
			SendMessage(((SIT_ToolBar)w)->tb_Handle, TB_AUTOSIZE, 0, 0);
		ret = 1;
		break;

	case WM_COMMAND: /* Control event */
		if (lParam == 0 && LOWORD(wParam) > 0)
		{
			int menuid = LOWORD(wParam);
			SIT_HandleAutoMenu(w, menuid);
			SIT_ApplyCallback(w, (APTR) menuid, SITE_OnMenu);
			return 0;
		}

		w = (SIT_Widget) GetProp((HWND) lParam, sit_class);

		if (w == NULL) break;

		switch (w->sw_Type) {
		case SIT_PUSHBUTTON:
			#define	pb   ((SIT_PushButton) w)

			switch (pb->sb_Type) {
			case SITV_RadioButton:
				SIT_UncheckRadio(w);
				// no break;
			case SITV_CheckBox:
			case SITV_3StateCB:
				pb->sb_State = SendMessage(w->sw_Handle, BM_GETSTATE, 0, 0) &
							   (BST_CHECKED | BST_INDETERMINATE);
				if (pb->sb_CurValue) SIT_SyncCBValue(pb);
				if (HAS_EVT(w, SITE_OnChanged))
					SIT_ApplyCallback(w, (APTR) pb->sb_State, SITE_OnChanged);
				// no break;
			default:
				if (HIWORD(wParam) == BN_CLICKED)
					SIT_ApplyCallback(w, (APTR) pb->sb_State, SITE_OnActivate);
			}
			#undef pb
			break;

		case SIT_EDITBOX:
			#define	edit ((SIT_EditBox)w)
			if (edit->se_UpDown && edit->se_CurValue)
			{
				SIT_EditGetText(edit);
				*edit->se_CurValue = lParam = w->sw_Title ? strtoul(w->sw_Title, 0, 10) : 0;
				SIT_ApplyCallback(w, (APTR) lParam, SITE_OnScroll);
			}
			else switch (HIWORD(wParam)) {
			case EN_CHANGE:    SIT_HandleEdit(w, SITE_OnChanged); edit->se_CancelEvent = False; break;
			case EN_KILLFOCUS: SIT_ApplyCallback(w, NULL, SITE_OnBlur);  break;
			case EN_SETFOCUS:  SIT_ApplyCallback(w, NULL, SITE_OnFocus); break;
			case EN_UPDATE:    SIT_HandleEdit(w, SITE_OnChange); break;
			}
			#undef edit
			break;
		case SIT_TOOLBAR:
			if (((SIT_ToolBar)w)->tb_Style & SITV_SyncWithMenu)
				SIT_ToolbarSyncWithMenu(w, wParam);
			if (! HAS_EVT(w, SITE_OnMenu))
			{
				/* Notify dialog (usually menu bar event handler) */
				while (w->sw_Type != SIT_DIALOG)
					w = w->sw_Parent;
			}
			SIT_ApplyCallback(w, (APTR) wParam, SITE_OnMenu);
			break;
		case SIT_COMBOBOX:
			if (HIWORD(wParam) == CBN_SELCHANGE && HAS_EVT(w, SITE_OnMenu))
			{
				ULONG sel = SendMessage(w->sw_Handle, CB_GETCURSEL, 0, 0);
				SIT_ApplyCallback(w, (APTR) sel, SITE_OnMenu);
			}
		default: break;
		}
		break;

	case WM_CONTEXTMENU:
		if (HAS_EVT(w, SITE_OnCtxMenu))
		{
			POINT pt = {.x = GET_X_LPARAM(lParam), .y = GET_Y_LPARAM(lParam)};
			ScreenToClient(w->sw_Handle, &pt);
			/* Get rid of those NIH types :-/ */
			int pts[] = {pt.x, pt.y};
			SIT_ApplyCallback(w, pts, SITE_OnCtxMenu);
		}
		else return DefWindowProc(wnd, message, wParam, lParam);
		break;

	/* Menu with bitmap icon */
	case WM_MEASUREITEM:
		if (wParam == 0) /* Menu only */
			return SIT_MeasureMenu(w, (APTR) lParam);
		return FALSE;

	/* Mouse buttons */
	case WM_LBUTTONDOWN:   param ++; // no break;
	case WM_LBUTTONUP:     param ++; // no break;
	case WM_LBUTTONDBLCLK: param ++; // no break;
	case WM_RBUTTONDOWN:   param ++; // no break;
	case WM_RBUTTONUP:     param ++; // no break;
	case WM_RBUTTONDBLCLK: param ++; // no break;
	case WM_MBUTTONDOWN:   param ++; // no break;
	case WM_MBUTTONUP:     param ++; // no break;
	case WM_MBUTTONDBLCLK: param ++; // no break;
/*	case WM_XBUTTONDOWN:   param ++; // no break;
	case WM_XBUTTONUP:     param ++; // no break;
	case WM_XBUTTONDBLCLK: param ++; // no break; */
	case WM_MOUSEMOVE:     param ++;
	{	static char state[] = {4, 3, 2|32,1|32,0|32, 2|16,1|16,0|16, 2,1,0};
		SIT_OnMouse msg;

		if (message != WM_MOUSEMOVE && w->sw_Type == SIT_CANVAS && ((SIT_Canvas)w)->sc_AutoFocus)
			SetFocus(w->sw_Handle);

		if (HAS_1OF2EVT(w, SITE_OnMouseMove, SITE_OnClick) == 0)
			return DefWindowProc(wnd, message, wParam, lParam);

		if (w->sw_ButtonCapture > 0 && param == 1)
			param = 0;

		msg.button = state[param] >> 4;
		msg.state  = state[param] & 15;
		msg.x      = (int16_t) LOWORD(lParam);
		msg.y      = (int16_t) HIWORD(lParam);
		msg.flags  = 0;
		if (wParam & MK_SHIFT)   msg.flags |= SITK_FlagShift;
		if (wParam & MK_CONTROL) msg.flags |= SITK_FlagCtrl;

		if (w->sw_ButtonCapture > 0 && w->sw_ButtonCapture == msg.button + 1 &&
			param > 1 && msg.state > 0)
			ReleaseCapture(), w->sw_ButtonCapture = 0;

		param = SIT_ApplyCallback(w, &msg,
			msg.state >= 3 ? SITE_OnMouseMove : SITE_OnClick);

		/* Capture the mouse in a press-move-release sequence */
		if ((msg.state == 0 || msg.state == 2) && param == 1)
			SetCapture(w->sw_Handle), w->sw_ButtonCapture = msg.button + 1;
	}	break;

	case WM_MOUSEWHEEL:
	{	/* (scroll value / WHEEL_DELTA ) * lines-to-scroll user setting = lines to scroll */
		int lines = 0;
		SIT_OnMouse msg;
		if (! HAS_EVT(w, SITE_OnClick)) break;
		SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &lines, 0);
		if (lines >= 0)
		{
			SIT_Dialog dlg = (SIT_Dialog) w;
			/* Get dialog widget */
			while (dlg->super.sw_Type != SIT_DIALOG)
				dlg = (SIT_Dialog) dlg->super.sw_Parent;

			lines *= (int16_t) HIWORD(wParam);

			/* If scroll change direction, reset accumulator */
			if (lines * dlg->sd_ScrollWheel >= 0)
				lines += dlg->sd_ScrollWheel;

			msg.state = lines / WHEEL_DELTA;
			dlg->sd_ScrollWheel = lines % WHEEL_DELTA;
			dlg->sd_WheelWnd = wnd;

			/* Not enough accumulated to scroll */
			if (msg.state == 0) return 0;
		}
		else msg.state = (int16_t) HIWORD(wParam) < 0 ? SITOM_PrevPage : SITOM_NextPage;

		{	POINT pt = {.x = LOWORD(lParam), .y = HIWORD(lParam)};
			ScreenToClient(wnd, &pt);
			msg.x = pt.x;
			msg.y = pt.y;
			msg.button = 3;
			msg.flags  = 0;
		}
		if (wParam & MK_SHIFT)   msg.flags |= SITK_FlagShift;
		if (wParam & MK_CONTROL) msg.flags |= SITK_FlagCtrl;
		SIT_ApplyCallback(w, &msg, SITE_OnClick);
	}	break;

	case WM_VSCROLL: // Scroll bar or track bar
	case WM_HSCROLL:
		w = (SIT_Widget) GetProp((HWND) lParam, sit_class);
		if (w == NULL) break;

		switch (w->sw_Type) {
		case SIT_SCROLLBAR: SIT_HandleScrollBar(w, LOWORD(wParam)); break;
		case SIT_SLIDER:    SIT_HandleSlider(w, LOWORD(wParam), True); break;
		case SIT_EDITBOX:   // Up/down buddy
			#define	edit ((SIT_EditBox)w)
			*edit->se_CurValue = lParam = SendMessage(edit->se_UpDown, UDM_GETPOS32, 0, 0);
			if (LOWORD(wParam) == SB_THUMBPOSITION)
				SIT_ApplyCallback(w, (APTR) lParam, SITE_OnScroll);
			#undef edit
		default: break;
		}
		break;

	case WM_CTLCOLORBTN: // Custom control colors
	case WM_CTLCOLORSTATIC:
	case WM_CTLCOLOREDIT:
		w = (SIT_Widget) GetProp((HWND) lParam, sit_class);

		if (w == NULL) return DefWindowProc(wnd, message, wParam, lParam);

		LRESULT result = 0;
		if (w->sw_Flags & SITF_HasCustBg)
		{
			if (w->sw_Bg == SYSCOL(GFX_ColorTransparent))
			{
				SetBkMode((HDC) wParam, TRANSPARENT);
				result = (LRESULT) GetStockBrush(HOLLOW_BRUSH);
			}
			else
			{
				ULONG color = ISSYS(w->sw_Bg) ? GetSysColor(sysColorsWin32[w->sw_Bg & 0xffff]) : w->sw_Bg;
				SetBkColor((HDC) wParam, color);
				if (w->sw_BgBrush == NULL)
					w->sw_BgBrush = CreateSolidBrush(color);
				result = (LRESULT) w->sw_BgBrush;
			}
		}
		else if (w->sw_Parent->sw_Type != SIT_TAB || w->sw_Type != SIT_LABEL)
		{
			result = DefWindowProc(wnd, message, wParam, lParam);
		}
		else result = 0;

		if (w->sw_Flags & SITF_HasCustFg)
		{
			COLORREF color = w->sw_Fg;
			if (ISSYS(color))
				color = GetSysColor(sysColorsWin32[color & 0xffff]);
			SetTextColor((HDC) wParam, color);
		}
		return result;

	case WM_NOTIFY:
		#define	nmhdr    ((LPNMHDR)lParam)
		w = (APTR) GetProp(nmhdr->hwndFrom, sit_class);
		if (w == NULL) return 0;
		switch (nmhdr->code) {
		case NM_CLICK:
		case NM_RCLICK:
		case NM_DBLCLK:
		case NM_RDBLCLK:
		case NM_RETURN:
			if (w->sw_Type == SIT_LABEL) // HTML Link
			{
				PNMLINK link = (PNMLINK)lParam;

				if (SIT_ApplyCallback(w, (APTR) link->item.iLink, SITE_OnActivate) == 0 &&
				    link->item.szUrl[0])
				{
					ShellExecute(NULL, L"open", link->item.szUrl, NULL, NULL, SW_SHOW);
				}
			}
			else if (HAS_EVT(w, SITE_OnClick) && w->sw_Type != SIT_TAB)
			{
				LPNMMOUSE   nm  = (APTR) lParam;
				SIT_OnMouse msg = {.x = nm->pt.x, .y = nm->pt.y};

				msg.state = nmhdr->code == NM_CLICK || nmhdr->code == NM_RCLICK ?
					SITOM_ButtonReleased : SITOM_DoubleClick;
				msg.button = nmhdr->code == NM_CLICK || nmhdr->code == NM_DBLCLK ? 0 : 1;
				if (GetKeyState(VK_SHIFT))   msg.flags |= SITK_FlagShift;
				if (GetKeyState(VK_CONTROL)) msg.flags |= SITK_FlagCtrl;
				if (GetKeyState(VK_MENU))    msg.flags |= SITK_FlagAlt;
				if (w->sw_Type == SIT_STATUSBAR)
					((SIT_Status)w)->st_StatusPart = (ULONG) nm->dwItemSpec;

				return SIT_ApplyCallback(w, &msg, SITE_OnClick);
			}
			break;

		case DTN_DATETIMECHANGE:
			SIT_SyncDate(w, &((LPNMDATETIMECHANGE)lParam)->st);
			if (HAS_EVT(w, SITE_OnChange))  SIT_ApplyCallback(w, &((SIT_DateTime)w)->sd_CurDate, SITE_OnChange); else
			if (HAS_EVT(w, SITE_OnChanged)) SIT_ApplyCallback(w, &((SIT_DateTime)w)->sd_CurDate, SITE_OnChanged);
			break;

		case NM_CUSTOMDRAW:
			if (w->sw_Type == SIT_TOOLBAR)
			{
				LPNMTBCUSTOMDRAW cd = (LPVOID) lParam;
				switch (cd->nmcd.dwDrawStage) {
				case CDDS_PREPAINT:
					return ((SIT_ToolBar)w)->tb_HasCustCtrl ? CDRF_DODEFAULT : CDRF_NOTIFYITEMDRAW;
				case CDDS_ITEMPREPAINT:
					return cd->nmcd.dwItemSpec >= TB_CUSTOMCTRL ? CDRF_SKIPDEFAULT : CDRF_DODEFAULT;
				}
			}
			else if (w->sw_Type == SIT_LISTBOX)
			{
				return HAS_EVT(w, SITE_OnPaint) ? SIT_HandleListCustomDraw(w, (LPVOID) lParam) : 0;
			}
			else if (HAS_EVT(w, SITE_OnPaint))
			{
				LPNMCUSTOMDRAW cd = (APTR) lParam;
				GCBuf gc;

				switch (cd->dwDrawStage) {
				case CDDS_PREPAINT: return CDRF_NOTIFYITEMDRAW;
				case CDDS_ITEMPREPAINT:
					GFX_InitGC(&gc, w->sw_Handle, cd->hdc, &cd->rc, NULL, w->sw_BgBrush);
					gc.stage = cd->dwItemSpec;
					int ret = SIT_ApplyCallback(w, &gc, SITE_OnPaint);
					GFX_Free(&gc);
					return ret ? CDRF_SKIPDEFAULT : CDRF_DODEFAULT;
				}
			}
			break;

		case LVN_ITEMACTIVATE: // List-view double-click on item
			if (HAS_EVT(w, SITE_OnActivate))
			{
				LPNMITEMACTIVATE ia = (APTR) lParam;
				SIT_ApplyCallback(w, (APTR) ia->iItem, SITE_OnActivate);
			}
			break;

		case LVN_ITEMCHANGED:
			((SIT_ListBox)w)->sl_SelIndex = ListView_GetNextItem(w->sw_Handle, -1, LVIS_SELECTED);
			if (HAS_1OF2EVT(w, SITE_OnChanged, SITE_OnChange) &&
			    (w->sw_Flags & SITF_ListEvt) == 0)
			{
				w->sw_Flags |= SITF_ListEvt;
				PostMessageW(w->sw_Handle, WM_APP+1, 0, 0);
			}
			break;

		case TCN_SELCHANGE: /* Tab control */
			w = (APTR) GetProp(nmhdr->hwndFrom, sit_class);

			param = TabCtrl_GetCurSel(w->sw_Handle);

			if (w && ((SIT_Tab)w)->st_CurTab != param) {
				((SIT_Tab)w)->st_CurTab = param;
				SIT_ApplyCallback(w, (APTR) param, SITE_OnChanged);
			}
			break;

		case TBN_DROPDOWN: /* Toolbar button with drop down */
			w = (APTR) GetProp(nmhdr->hwndFrom, sit_class);
			if (w)
			{
				LPNMTOOLBAR nmtb = (APTR) lParam;
				int pts[3];
				pts[0] = nmtb->iItem;
				pts[1] = nmtb->rcButton.left;
				pts[2] = nmtb->rcButton.bottom ;
				if (has_syslink) pts[1] ++, pts[2] ++;
				return SIT_ApplyCallback(w, pts, SITE_OnCtxMenu) ?
					TBDDRET_DEFAULT : TBDDRET_TREATPRESSED;
			}
			else return TBDDRET_TREATPRESSED;

		case TBN_GETINFOTIP:
			w = (APTR) GetProp(nmhdr->hwndFrom, sit_class);
			if (w && w->sw_Type == SIT_TOOLBAR)
			{
				LPNMTBGETINFOTIP tip = (APTR) lParam;
				tip->pszText = SIT_ToolbarGetTip(w, tip->iItem);
			}
		}
		#undef	nmhdr
		break;

	case WM_DRAWITEM: /* Custom statusbar, owner drawn combobox or menu icons */
	{	LPDRAWITEMSTRUCT dis = (APTR) lParam;
		if (wParam == 0)
		{
			/* Menu icons */
			SIT_DrawMenuItem(w, dis);
			return TRUE;
		}
		wnd = GetDlgItem(wnd, wParam);
		w = (APTR) GetPropW(wnd, sit_class);
		if (! HAS_EVT(w, SITE_OnPaint))
			return FALSE;
		if (dis->itemAction == ODA_FOCUS)
		{
			if (!(dis->itemState & ODS_NOFOCUSRECT))
				DrawFocusRect(dis->hDC, &dis->rcItem);
			return TRUE;
		}
		else
		{
			GCBuf gc;
			GFX_InitGC(&gc, w->sw_Handle, dis->hDC, &dis->rcItem, w->sw_Font, w->sw_BgBrush);
			GFX_SetDrMd(&gc, GFX_MODEJAM1);
			gc.stage = dis->itemID;
			if (dis->itemState & ODS_SELECTED) gc.stage |= 1 << 31;
			SIT_ApplyCallback(w, &gc, SITE_OnPaint);
			GFX_Free(&gc);

			if ((dis->itemState & ODS_FOCUS) && ! (dis->itemState & ODS_NOFOCUSRECT))
				DrawFocusRect(dis->hDC, &dis->rcItem);
			return TRUE;
		}
	}	break;

	case WM_SETFOCUS:
		#define canvas ((SIT_Canvas)w)
		if (w->sw_Type == SIT_CANVAS && canvas->sc_AutoFocus) {
			canvas->sc_HasFocus = True;
			SIT_RefreshBorder(w);
		}
		if (HAS_EVT(w, SITE_OnFocus))
			SIT_ApplyCallback(w, (APTR) 1, SITE_OnFocus);
		break;
	case WM_KILLFOCUS:
		if (w->sw_Type == SIT_CANVAS && canvas->sc_AutoFocus) {
			canvas->sc_HasFocus = False;
			SIT_RefreshBorder(w);
		}

		if (HAS_EVT(w, SITE_OnBlur))
			SIT_ApplyCallback(w, NULL, SITE_OnBlur);

		/* Reset accumulated scroll wheel value */
		while (w && w->sw_Type != SIT_DIALOG)
			w = w->sw_Parent;

		if (w)
		{
			((SIT_Dialog) w)->sd_ScrollWheel = 0;
			((SIT_Dialog) w)->sd_WheelWnd = 0;
		}
		break;
		#undef canvas

	case WM_ONUSER:
		SIT_ApplyCallback(w, (APTR) lParam, SITE_OnUser);
		break;

	case WM_COMBOCHG: /* ComboBox crappy list management */
		w = (APTR) GetProp((HWND) lParam, sit_class);

		if (w) SIT_SetComboListWithTags((APTR) w);
		break;

	case WM_GEOMETRY: /* Async geometry change */
		#ifdef DEBUG
		fprintf(stderr, "WM_GEOMETRY for '%S': %ld\n", w->sw_Name, lParam);
		#endif
		if (w->sw_ResizePolicy == SITV_Optimal)
			memset(&((SIT_Dialog)w)->sd_MinSize, 0, 2 * sizeof (SIZE));
		if ((w->sw_Flags & (SITF_FixedWidth | SITF_FixedHeight)) == (SITF_FixedWidth | SITF_FixedHeight))
			lParam = KeepDialogSize;
		w->sw_Flags &= ~SITF_GeomNotified;
		SIT_LayoutWidgets(w, lParam);
		SIT_MoveWidgets(w);
		break;

	case WM_DELAYDESTROY: /* Wait until user callbacks have returned */
		w = (APTR) GetProp((HWND) lParam, sit_class);
		SIT_RemoveWidget(w);
		return 0;

	case WM_GETMINMAXINFO:
		if (w && w->sw_Type == SIT_DIALOG)
		{
			#define	d     ((SIT_Dialog) w)
			LPMINMAXINFO mmi = (APTR) lParam;
			RECT rect;
			SIT_AdjustWindowRect((APTR)w, &rect, NULL);
			if (w->sw_Flags & SITF_CanResizeW)
				mmi->ptMinTrackSize.x = d->sd_MinSize.cx,
				mmi->ptMaxTrackSize.x = d->sd_MaxSize.cx;
			else
				mmi->ptMinTrackSize.x = mmi->ptMaxTrackSize.x = rect.right - rect.left;
			if (w->sw_Flags & SITF_CanResizeH)
				mmi->ptMinTrackSize.y = d->sd_MinSize.cy,
				mmi->ptMaxTrackSize.y = d->sd_MaxSize.cy;
			else
				mmi->ptMinTrackSize.y = mmi->ptMaxTrackSize.y = rect.bottom - rect.top;
			#undef	d
			return 0;
		}
		else return DefWindowProc(wnd, message, wParam, lParam);

	case WM_CHANGECBCHAIN:
		/* If the next window is closing, repair the chain */
		if ((HWND) wParam == w->sw_Chain)
			w->sw_Chain = (HWND) lParam;
		/* Otherwise, pass the message to the next link */
		else if (w->sw_Chain != NULL)
			SendMessage(w->sw_Chain, message, wParam, lParam);
		break;

	case WM_DRAWCLIPBOARD:
		/* Pass the message to the next window in clipboard viewer chain */
		if (HAS_EVT(w, SITE_OnClipChanged)) // Should always be true
		{
			SIT_ApplyCallback(w, NULL, SITE_OnClipChanged);
		}
		SendMessage(w->sw_Chain, message, wParam, lParam);
		break;

	case WM_RENDERFORMAT:
		if (HAS_EVT(w, SITE_OnClipRender))
		{
			STRPTR fmt = SIT_FormatName(wParam);
			if (fmt == NULL) return 0;
			w->sw_Flags |= SITF_Internal;
			SIT_ApplyCallback(w, fmt, SITE_OnClipRender);
			w->sw_Flags &= ~SITF_Internal;
		}
		return 0;
	case WM_RENDERALLFORMATS:
		if (HAS_EVT(w, SITE_OnClipRender))
		{
			STRPTR * fmts = app->sa_Formats;
			int i;
			if (fmts == NULL) return 0;
			if (OpenClipboard(w->sw_Handle) && EmptyClipboard())
			{
				w->sw_Flags |= SITF_Internal;
				for (i = 0; fmts[i]; i ++)
					SIT_ApplyCallback(w, fmts[i], SITE_OnClipRender);
				w->sw_Flags &= ~SITF_Internal;
				CloseClipboard();
			}
		}
		return 0;

	case WM_ERASEBKGND:
		/* Application will handle background erasure */
		if (w->sw_Flags & SITF_HandleEraseBg)
		{
			SIT_Canvas c = (APTR) w;
			RECT       r;
			GetClientRect(wnd, &r);
			if (w->sw_Flags & SITF_PostProcess)
			{
				if (c->sc_FullErase)
				{
					/* OnNCPaint implies EraseBg flag, even if user didn't set it */
					FillRect((HDC) wParam, &r, w->sw_BgBrush);
				}
				else /* ERASEBKGND from resize event: limit flickering effect */
				{
					if (r.right > c->sc_Box.cx) {
						RECT rd = {.left = c->sc_Box.cx, .right = r.right, .top = 0, .bottom = r.bottom};
						FillRect((HDC) wParam, &rd, w->sw_BgBrush);
					}
					if (r.bottom > c->sc_Box.cy) {
						RECT rd = {.left = 0, .right = c->sc_Box.cx, .top = c->sc_Box.cy, .bottom = r.bottom};
						FillRect((HDC) wParam, &rd, w->sw_BgBrush);
					}
				}
				w->sw_Flags &= ~SITF_PostProcess;
			}
			else /* ERASEBKGND from refresh: clear whole area (which is clipped) */
			{
				FillRect((HDC) wParam, &r, w->sw_BgBrush);
			}
			return 1;
		}
		else return DefWindowProc(wnd, message, wParam, lParam);

	case WM_SETCURSOR:
		w = (SIT_Widget) GetProp((HWND) wParam, sit_class);
		if (w && w->sw_Cursor)
		{
			SetCursor(w->sw_Cursor);
			return TRUE;
		}
		else return DefWindowProc(wnd, message, wParam, lParam);

	case WM_NCCALCSIZE:
		/* Resizable plain dialog type needs THICKFRAME from size gripper to work */
		if (w->sw_Type == SIT_DIALOG && ((SIT_Dialog)w)->sd_Type == SITV_Plain)
			return 1;

		ret = DefWindowProc(wnd, message, wParam, lParam);

		if (HAS_EVT(w, SITE_OnNCCalcSize))
		{
			LPNCCALCSIZE_PARAMS pncc = (LPNCCALCSIZE_PARAMS)lParam;

			SIT_ApplyCallback(w, pncc->rgrc, SITE_OnNCCalcSize);
		}
		break;
	case WM_NCPAINT:
		if (HAS_EVT(w, SITE_OnNCPaint))
		{
			SIT_RefreshBorder(w);
			break;
		}
		else return DefWindowProc(wnd, message, wParam, lParam);

	case WM_INITMENU:
		if (HAS_EVT(w, SITE_OnInitMenu))
			SIT_ApplyCallback(w, NULL, SITE_OnInitMenu);
		return 0;

	case WM_DROPFILES:
		SIT_HandleDropFiles(w, (HANDLE) wParam);
		return 0;


	/*
	 * Since we are calling IsDialogMessage for every window, keyboard messages
	 * will get reported through WM_GETDLGCODE to get a chance to cancel the
	 * default actions for some keys (tab, arrow keys, esc, return, ...)
	 */
	case WM_GETDLGCODE:
		if (lParam)
		{
			/* Check if in the hierarchy, there is a control that redirect keyboard */
			while (w && ! HAS_1OF2EVT(w, SITE_OnRawKey, SITE_OnVanillaKey)) {
				if (w->sw_Type == SIT_DIALOG) { w = NULL; break; }
				w = w->sw_Parent;
			}

			if (w)
			{
				LPMSG msg = (LPMSG) lParam;

				switch (msg->message) {
				case WM_DEADCHAR: param = SITK_FlagDead; goto case_WM_KEY;
				case WM_CHAR:     param = 0;             goto case_WM_KEY;
				case WM_KEYDOWN:  param = SITK_Virtual;
				case_WM_KEY:
					if (SIT_HandleKeyboard(w, msg->wParam, param))
						return DLGC_WANTMESSAGE;
				}
			}
		}
		return 0; /* Do the default action */

	case WM_THEMECHANGED:
		has_theme = pIsThemeActive();
		// no break;

	default: /* for messages that we don't deal with */
		if (w && w->sw_SuperProc)
			return CallWindowProc(w->sw_SuperProc, wnd, message, wParam, lParam);
		else
			return DefWindowProc(wnd, message, wParam, lParam);
	}

	/* If a callback allocated a GC, time to free it now */
	if (w && w->sw_GC) ((GC)w->sw_GC)->inuse = 0, GFX_Free(w->sw_GC);
	if (w && (w->sw_Flags & SITF_BeingDestroyed)) {
		SIT_DestroyChildren(w);
		SIT_DestroyWidget(w);
	}

	return ret;
}
